package vivace.model;


/**
 * Class modeling a note in the notation view.
 *
 */
public class Note extends MusicalObject {
    
	/* Unicode characters for the different note signs in the Euterpe font */
	public static final String SHARP = "\u266f";
    public static final String FLAT = "\u266e";
    public static final String WHOLE = "\uD834\uDD5D";
    public static final String HALF = "\uD834\uDD5E";
    public static final String QUARTER = "\uD834\uDD5F";
    public static final String EIGHTH = "\uD834\uDD60";
    public static final String SIXTEENTH = "\uD834\uDD61";
    public static final String THIRTYSECOND = "\uD834\uDD62";
    public static final String SIXTYFOURTH = "\uD834\uDD63";
    // Debug variables
    public int index;

    /*
       public static final String INVISIBLE_BEGIN_TIE = "\uD834\uDD75";
       public static final String INVISIBLE_END_TIE = "\uD834\uDD76";
       */

    public String noteName;
    private int number;
    private Note tiedTo; 
    private Note tiedFrom; 
    private Bar myBar;
    private int relOffset;
    private int posInOctave;
    private long res;
    private boolean iAmSharpNote;
    private boolean displaySharp;
    private boolean relOffsetCalculated; // So that we won't calculate this more than we need to, and always calculate it when we need to.
    private boolean middleCness;

//    public Note(){ // a dummy note, to get the constants out. Can also be changed using the set functions below.
//        this(0, 0);
//    }

    /**
     * @param number The MIDI note height number for the Note.
     * @param length The length of the note, in ticks.
     */
    public Note(int number, long length, long PPQResolution) {
        this.length = length;
        this.number = number;
        res = PPQResolution;
        // Setting this to false makes us calculate the offset.
        relOffsetCalculated = false;
        calculateOffset();
    }


    /**
     * This sets wether to display sharp signs or flat signs.
     * Setting this to true makes a sharp note be displayed with a sharp sign.
     * Setting this to false makes a flat note be displayed with a flat sign.
     */
    public void setDisplaySharp(boolean sharp){
        displaySharp = sharp;
    }

    public boolean displaysAccidental(){
        return displaySharp == iAmSharpNote && tiedFrom == null;
    }

    /**
     * Tells wether this is a sharp note or not.
     */
    // Skall den här eller någon avart därav vara publik?
    public boolean isSharp(){
        calculateOffset();
        return iAmSharpNote;
    }


    /**
     * Get the offset for the current note, in semi notes relative "middle-C"
     */
    private void calculateOffset(){

        // If we're already calculated this, there is no need to do it again.
        if(relOffsetCalculated) 
    		return;

        // Vi börjar på mitten-C, så blir G-klaven positiv och F-klaven negativ! Ftw! 
        int i = number - 60; 
        // F ger index 0...
        relOffset = 0;
        // We only need to calculate the relOffset if we are not at index 0!
        if( i != 0){ 
                // A new octave every 12 semi notes.
                relOffset += 7*(i/12); 
                // Remove all excess octaves from i.
                i%=12;
                iAmSharpNote = false;
                middleCness = false;
            if( i > 0 ){
                switch(i){
                    case 5: //F
                        noteName="F";
                        relOffset+=3;
                        posInOctave = 3;
                        break;
                    case 6: //Fiss
                        noteName="Fiss";
                        relOffset+=3;
                        iAmSharpNote=true;
                        posInOctave = 3;
                        break;
                    case 7: //G
                        noteName="G";
                        relOffset+=4;
                        posInOctave = 4;
                        break;
                    case 8: //Giss
                        noteName="Giss";
                        relOffset+=4;
                        iAmSharpNote=true;
                        posInOctave = 4;
                        break;
                    case 9: //A
                        noteName="A";
                        relOffset+=5;
                        posInOctave = 5;
                        break;
                    case 10: //Aiss
                        noteName="b";
                        relOffset+=5;
                        iAmSharpNote=true;
                        posInOctave = 5;
                        break;
                    case 11: //H
                        noteName="H";
                        relOffset+=6;
                        posInOctave = 6;
                        break;
                        ///////////////////////////////////
                    case 0: //C
                        noteName="C";
                        relOffset+=0;
                        posInOctave = 0;
                        break;
                    case 1: //Ciss
                        noteName="Ciss";
                        relOffset+=0;
                        iAmSharpNote = true;
                        posInOctave = 0;
                        break;
                    case 2: //D
                        noteName="D";
                        relOffset+=1;
                        posInOctave = 1;
                        break;
                    case 3://Diss
                        noteName="Diss";
                        relOffset+=1;
                        iAmSharpNote = true;
                        posInOctave = 1;
                        break;
                    case 4://E
                        noteName="E";
                        relOffset+=2;
                        posInOctave = 2;
                        break;
                }
            }
            else if (i < 0) {
                relOffset-=7; //Change the relative octave.
                switch(i){
                    case -7: //F
                        noteName="F";
                        relOffset+=3;
                        posInOctave = 3;
                        break;
                    case -6: //Fiss
                        noteName="Fiss";
                        relOffset+=3;
                        iAmSharpNote=true;
                        posInOctave = 3;
                        break;
                    case -5: //G
                        noteName="G";
                        relOffset+=4;
                        posInOctave = 4;
                        break;
                    case -4: //Giss
                        noteName="Giss";
                        relOffset+=4;
                        iAmSharpNote=true;
                        posInOctave = 4;
                        break;
                    case -3: //A
                        noteName="A";
                        relOffset+=5;
                        posInOctave = 5;
                        break;
                    case -2: //Aiss
                        noteName="b";
                        relOffset+=5;
                        iAmSharpNote=true;
                        posInOctave = 5;
                        break;
                    case -1: //H
                        noteName="H";
                        relOffset+=6;
                        posInOctave = 6;
                        break;
                        ///////////////////////////////////
                    case 0: //C
                        noteName="C";
                        relOffset+=0;
                        posInOctave = 0;
                        break;
                    case -11: //Ciss
                        noteName="Ciss";
                        relOffset-=0;
                        iAmSharpNote = true;
                        posInOctave = 0;
                        break;
                    case -10: //D
                        noteName="D";
                        relOffset+=1;
                        posInOctave = 1;
                        break;
                    case -9://Diss
                        noteName="Diss";
                        relOffset+=1;
                        iAmSharpNote = true;
                        posInOctave = 1;
                        break;
                    case -8://E
                        noteName="E";
                        relOffset+=2;
                        posInOctave = 2;
                        break;
                }
            } 
        } else {
    		middleCness = true;
    	}

        // All is done, return the relOffset.
        relOffsetCalculated = true;

    }
    /**
     * Sets what bar this note belongs to.
     */

    public void setBar(Bar bar) {
        myBar = bar;
    }

    /**
     * Gets what bar this note belongs to.
     * @return What bar this note belongs to
     */

    public Bar getBar() {
        return myBar;
    }


    /**
     * Gets the name of the note.
     */
    public String getName(){
        return noteName;
    }

    /**
     * TODO: Give an explanation for this monster.
     */
    public int getPosInOctave(){
        return posInOctave;
    }


    /**
     * Gives how many notes (visually) away from the middle C the current note is.
     * @return How many notes away from the middle C the current note is. Positive values indicates notes higher than the C, negative values lower notes.
     */

    public int getRelOffset(){
        calculateOffset();
        return relOffset;
    }

    /**
     * Returns the MIDI note height number for the current Note.
     * Deprecated. Use getMIDINumber() instead.
     */
    public int getNumber() {
        return number;
    }

    /**
     * Returns the MIDI note height number for the current Note.
     */
    public int getMIDINumber() {
        return number;
    }

    
    /**
     * Tells wether this is a middle-C, which should be handled separately (until there is a Good Way to add a line over a note in Unicode)
     */
    
    public boolean isMiddleC(){
        return middleCness;
    }


    /**
     * Returns wether the Note is tied on either side or not.
     */

    public boolean isTied() {
        return tiedTo != null || tiedFrom !=null;
    }

    /**
     * Makes a tie from this Note to another Note.
     */
    public void tieTo(Note note) {
        tiedTo = note;
        note.tiedFrom = this;
    }

    /**
     * Returns the Note frow which there is a tie to this Note.
     * @return The Note tied from.
     */

    public Note getNoteTiedFrom() {
        return tiedFrom;
    }


    /**
     * Returns the Note to which there is a tie from this Note.
     * @return The Note tied to.
     */
    public Note getNoteTiedTo() {
        return tiedTo;
    }

    /**
     * Removes the tie between the two notes, if such a tie exists.
     * removeTieTo checks that the two notes are, in fact, tied.
     * It then removes the tie between them.
     * If there is no tie, the function does nothing.
     * @param note The note to remove the tie to.
     */

    public void removeTieTo(Note note){
        if(tiedTo == note && note.tiedFrom == this){
            tiedTo = null;
            note.tiedFrom = null;
        }
    }

    /**
     * Takes in an offset, splits the current note at the offset and returns the part after the split as a new Note; the notes remain tied.
     * 
     * Throws an IndexOutOfBoundsException if the offset is greater than the length of the Note, or smaller than 0.
     * @param offset The offset where the note is to be split.
     * @return The Note after the split offset.
     */


    public Note splitAfter(long offset) throws IndexOutOfBoundsException{
        if(offset <= length && offset >= 0){
            long temp = length;
            length = offset;
            Note note = new Note(number, temp-offset, res);
            note.tiedFrom = this;
            note.tiedTo = tiedTo;
            if(note.tiedTo!=null) {
    			note.tiedTo.tiedFrom=note;
    		}
            tiedTo = note;

            return note;
        } else {
            System.out.printf("ERRORNOTE IS %d LONG, AND I CAN HAS LENGTH OF %d", offset, length);

            throw new IndexOutOfBoundsException(); // Well, an offset is an index, sort of...
        }
    }

    /**
     * Takes in the length of the note to be chopped off from the beginning of the current note; the notes remain tied.
     * Throws an IndexOutOfBoundsException if the length parameter is greater than the length of the Note, or smaller than 0.
     * @param length how much of the note to chop off.
     * @return The "chopped off" Note.
     */

    public Note splitBefore(long length) throws IndexOutOfBoundsException{
        if(length <= this.length && length >= 0){
            long temp = this.length;
            this.length = temp-length;
            //Note note = new Note(number,length,null,this);
            Note note = new Note(number,length, res);
            //this.tieFrom=note;
            note.tiedTo = this;
            note.tiedFrom = tiedFrom;
            if(note.tiedFrom!=null) {
        		note.tiedFrom.tiedTo = note;
    		}
            tiedFrom = note;

            return note;
        } else {
            throw new IndexOutOfBoundsException(); // Well, an offset is an index, sort of...
        }
    }

    /**
     * Returns the UTF-8 value for the (possibly sharp) note.
     */

    public long getRes(){
        return res;
    }

    public String toString() {
        // TODO: Only the first sharp note in a bar is drawn.
        // TODO: A non-sharp note drawn after a note with the same note value in a bar is definitely always drawn with a natural sign after. But only the first note.
        // TODO: Thus, making a note know where it is in a bar is quite crucial.
        float len = length; 
       // int res = Project.RESOLUTION;
        String sharp;
        // If we have a sharp note, and we are not in the middle or end of a note, and we are to display sharpness, display the sharp sign.
        // Similarily, if we have a flat note, and we are not in the middle or end of a not, and we are to display flatness, display the flat sign.
        if(tiedFrom != null) 
            sharp = "";
        else if(iAmSharpNote && displaySharp) 
            sharp = SHARP;
        else if(!displaySharp && !iAmSharpNote) 
            sharp = FLAT;
        else 
            sharp = "";




        if (res * 3.875 < len ){
            return sharp+WHOLE;
        } else if (res * 1.875 < len && len <= res * 3.875) {
            return sharp+HALF;
        } else if (res * 0.875 < len && len <= res * 1.875) {
            return sharp+QUARTER;
        } else if (res * 0.375 < len && len <= res * 0.875) {
            return sharp+EIGHTH;
        } else if (res * 0.125 < len && len <= res * 0.375) {
            return sharp+SIXTEENTH;
        } else if (len <= res * 0.125) {
            return sharp+THIRTYSECOND;
        } else {
            return null;
        }
    }
    /*
     * A "better approximating" function.
     * This will propably be fixed another way.
     * I hope.
     *
     -//             if (res * 3.875 < len && len < res * 4.125) {
     -                       // approximate whole note between res*(2 + 1 + 1/2 + 1/4 + 1/ 8 ) and res*(4 + 1/8)
     -                       return WHOLE;
     -//        } else if (res * 1.875 < len && len < res * 2.125) {
     -            // approximate half note between res*(1 + 1/2 + 1/4 + 1/ 8 ) and res*(2 + 1/8)
     -                       return HALF;
     -//             } else if (res * 0.875 < len && len < res * 1.125) {
     -           // approximate quarter note between res*(1/2 + 1/4 + 1/ 8 ) and res*(1 + 1/8)
     -                       return QUARTER;
     -               //} else if (res * 0.375 < len && len < res * 0.625) {
     -                       // approximate eighth note between res*(1/4 + 1/ 8 ) and res*(1/2 + 1/8)
     -                       return EIGHTH;
     -                       // approximate sixteenth note between res*(1/8) and res*(1/4 + 1/8)
     -                       return SIXTEENTH;
     -//             } else if (len == res * 0.125) {
     -                       // thirty-second note
     -                       return THIRTYSECOND;
     -               } else {
     -                       return null;
     -               }
     -       }
     */

public Note reverse(){
    Note tmp = tiedFrom;
    tiedFrom = tiedTo;
    tiedTo = tmp;
    return this;
}

public void unsafeSetNoteTiedFrom(Note note){
    tiedFrom = note;
}
public void unsafeSetNoteTiedTo(Note note){
    tiedTo = note;
}

}
